
#include <BWidget>

#include <BVBoxLayout>
#include "member_BVBoxLayout.h"
#include "member_BWidget.h"
#include "member_BSpacer.h"
#include "member_BSpring.h"

using namespace BWE;

#define member					(*(member_BVBoxLayout*)_ptr)
#define member_allocate()		_ptr = new member_BVBoxLayout(this)
#define member_release()		delete (member_BVBoxLayout*)_ptr

BVBoxLayout::BVBoxLayout(BWidget* host)
{
	member_allocate();
	if (host)
		host->setLayout(this);
}
BVBoxLayout::BVBoxLayout(const BSizePolicy& sizePolicy, BWidget* host)
{
	member_allocate();
	this->setSizePolicy(sizePolicy);
	if (host)
		host->setLayout(this);
}
BVBoxLayout::BVBoxLayout(Policy widthPolicy, Policy heightPolicy, BWidget* host)
{
	member_allocate();
	this->setSizePolicy(BSizePolicy(widthPolicy, heightPolicy));
	if (host)
		host->setLayout(this);
}
BVBoxLayout::~BVBoxLayout()
{
	member_release();
}

bool BVBoxLayout::addWidget(BWidget* widget)
{
	if (!widget)
		return false;

	if (this->host() == widget)
		return false;

	if (widget_member(widget)->layout == this)
		return true;

	member.items.append(LayoutItem());
	LayoutItem& item = member.items.last();
	item.widget = widget;
	item.attach(this->host());

	if (widget_member(widget)->layout)
		widget_member(widget)->layout->remove(widget);
	widget_member(widget)->layout = this;

	this->dirty();
	return true;
}
bool BVBoxLayout::addLayout(BLayout* layout)
{
	if (!layout || layout == this)
		return false;
	if (!member.items.append(LayoutItem()))
		return false;
	LayoutItem& item = member.items.last();
	item.layout = layout;
	item.attach(this->host());
	this->dirty();
	return true;
}
bool BVBoxLayout::addSpacer(BSpacer* spacer)
{
	if (!spacer)
		return false;

	if (spacer_member(spacer)->layout == this)
		return true;

	if (!member.items.append(LayoutItem()))
		return false;

	LayoutItem& item = member.items.last();
	item = spacer;

	if (spacer_member(spacer)->layout)
		spacer_member(spacer)->layout->remove(spacer);
	spacer_member(spacer)->layout = this;

	this->dirty();
	return true;
}
bool BVBoxLayout::addSpring(BSpring* spring)
{
	if (!spring)
		return false;

	if (spring_member(spring)->layout == this)
		return true;

	if (!member.items.append(LayoutItem()))
		return false;

	LayoutItem& item = member.items.last();
	item.spring = spring;

	if (spring_member(spring)->layout)
		spring_member(spring)->layout->remove(spring);
	spring_member(spring)->layout = this;

	this->dirty();
	return true;
}
bool BVBoxLayout::addSpring(float stretch)
{
	BSpring* spring = new BSpring(stretch);
	return addSpring(spring);
}

bool BVBoxLayout::setWidget(int pos, BWidget* widget)
{
	if (!widget)
		return false;

	if (!member.items.check(pos))
		return false;

	int index = member.items.find(widget);
	if (index >= 0)
	{
		if (index != pos)
		{
			LayoutItem item = member.items[index];
			member.items.remove(index);
			member.items.insert(pos, item);
			this->dirty();
		}
		return true;
	}

	LayoutItem& item = member.items[pos];
	if (this->host() && item.widget)
	{
		this->host()->removeChild(item.widget);
	}
	item.detach(this->host());
	item.widget = widget;
	item.attach(this->host());

	if (widget_member(widget)->layout)
		widget_member(widget)->layout->remove(widget);
	widget_member(widget)->layout = this;

	this->dirty();
	return true;
}
bool BVBoxLayout::setLayout(int pos, BLayout* layout)
{
	if (!layout)
		return false;
	if (!member.items.check(pos))
		return false;
	int index = member.items.find(layout);
	if (index >= 0)
	{
		if (index != pos)
		{
			LayoutItem item = member.items[index];
			member.items.remove(index);
			member.items.insert(pos, item);
			this->dirty();
		}
		return true;
	}
	LayoutItem& item = member.items[pos];
	item.detach(this->host());
	item.layout = layout;
	item.attach(this->host());
	this->dirty();
	return true;
}
bool BVBoxLayout::setSpacer(int pos, BSpacer* spacer)
{
	if (!spacer)
		return false;
	if (!member.items.check(pos))
		return false;
	int index = member.items.find(spacer);
	if (index >= 0)
	{
		if (index != pos)
		{
			LayoutItem item = member.items[index];
			member.items.remove(index);
			member.items.insert(pos, item);
			this->dirty();
		}
		return true;
	}
	member.items[pos].detach(this->host());
	member.items[pos] = spacer;
	spacer_member(spacer)->layout = this;
	this->dirty();
	return true;
}
bool BVBoxLayout::setSpring(int pos, BSpring* spring)
{
	if (!member.items.check(pos))
		return false;
	if (!spring)
		spring = new BSpring();
	int index = member.items.find(spring);
	if (index >= 0)
	{
		if (index != pos)
		{
			LayoutItem item = member.items[index];
			member.items.remove(index);
			member.items.insert(pos, item);
			this->dirty();
		}
		return true;
	}
	member.items[pos].detach(this->host());
	member.items[pos] = spring;
	spring_member(spring)->layout = this;
	this->dirty();
	return true;
}
bool BVBoxLayout::setSpring(int pos, float stretch)
{
	BSpring* spring = new BSpring(stretch);
	return setSpring(pos, spring);
}

bool BVBoxLayout::insertWidget(int pos, BWidget* widget)
{
	if (!widget)
		return false;

	int index = member.items.find(widget);
	if (index == pos)
		return true;

	if (index >= 0)
	{
		LayoutItem item = member.items[index];
		member.items.remove(index);
		member.items.insert(pos, item);
		this->dirty();
		return true;
	}

	if (!member.items.insert(pos, LayoutItem()))
		return false;

	LayoutItem& item = member.items[pos];
	item.detach(this->host());
	item.widget = widget;
	item.attach(this->host());

	if (widget_member(widget)->layout)
		widget_member(widget)->layout->remove(widget);
	widget_member(widget)->layout = this;

	this->dirty();
	return true;
}
bool BVBoxLayout::insertLayout(int pos, BLayout* layout)
{
	return false;
}
bool BVBoxLayout::insertSpacer(int pos, BSpacer* spacer)
{
	return false;
}
bool BVBoxLayout::insertSpring(int pos, BSpring* spring)
{
	return false;
}
bool BVBoxLayout::insertSpring(int pos, float stretch)
{
	return false;
}

int BVBoxLayout::count() const
{
	return member.items.size();
}
bool BVBoxLayout::remove(int pos)
{
	if (!member.items.check(pos))
		return false;
	member.items[pos].detach(this->host());
	member.items.remove(pos);
	this->dirty();
	return true;
}
BObject* BVBoxLayout::object(int pos)
{
	if (member.items(pos).widget)
		return member.items(pos).widget;
	if (member.items(pos).layout)
		return member.items(pos).layout;
	if (member.items(pos).spacer)
		return member.items(pos).spacer;
	if (member.items(pos).spring)
		return member.items(pos).spring;
	return 0;
}
BWidget* BVBoxLayout::widget(int pos)
{
	return member.items(pos).widget;
}
BLayout* BVBoxLayout::layout(int pos)
{
	return member.items(pos).layout;
}
BSpring* BVBoxLayout::spring(int pos)
{
	return member.items(pos).spring;
}

const BWidget* BVBoxLayout::widget(int pos) const
{
	return member.items(pos).widget;
}
const BLayout* BVBoxLayout::layout(int pos) const
{
	return member.items(pos).layout;
}
const BSpring* BVBoxLayout::spring(int pos) const
{
	return member.items(pos).spring;
}

BRect BVBoxLayout::cellRect(int pos)
{
	return member.items(pos).rect;
}

bool BVBoxLayout::setAlign(int pos, Align align)
{
	if (member.items.check(pos))
		return false;
	LayoutItem& item = member.items[pos];
	if (item.align != align)
	{
		item.align = align;
		this->dirty();
	}
	return true;
}
Align BVBoxLayout::align(int pos) const
{
	return member.items(pos).align;
}

bool BVBoxLayout::contain(const BWidget* widget) const
{
	if (!widget)
		return false;
	for (int i = 0; i < member.items.size(); i++)
	{
		const LayoutItem& item = member.items[i];
		if (item.widget == widget)
			return true;
		if (item.layout && item.layout->contain(widget))
			return true;
	}
	return false;
}
bool BVBoxLayout::contain(const BLayout* layout) const
{
	return false;
}
bool BVBoxLayout::contain(const BSpacer* spacer) const
{
	return false;
}
bool BVBoxLayout::contain(const BSpring* spring) const
{
	return false;
}

bool BVBoxLayout::remove(const BWidget* widget)
{
	if (!widget)
		return false;
	if (widget_member(widget)->layout == this)
	{
		widget_member(widget)->layout = 0;
		member.items.remove(widget);
		this->dirty();
		return true;
	}
	return true;
}
bool BVBoxLayout::remove(const BLayout* layout)
{
	if (!layout)
		return false;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		if (item.layout == layout)
		{
			item.layout->setHost(0);
			item.layout = 0;
			this->dirty();
			return true;
		}
	}
	return false;
}
bool BVBoxLayout::remove(const BSpacer* spacer)
{
	if (!spacer)
		return false;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		if (item.spacer == spacer)
		{
			item.spacer = 0;
			this->dirty();
			return false;
		}
	}
	return true;
}
bool BVBoxLayout::remove(const BSpring* spring)
{
	if (!spring)
		return false;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		if (item.spring == spring)
		{
			item.spring = 0;
			this->dirty();
			return false;
		}
	}
	return true;
}

void BVBoxLayout::setHost(BWidget* host)
{
	if (!host)
		return;
	if (this->contain(host))
		return;
	if (this->host() == host)
		return;
	if (this->host())
	{
		for (int i = 0; i < member.items.size(); i++)
		{
			LayoutItem& item = member.items[i];
			item.detach(this->host());
		}
	}
	BLayout::setHost(host);
	if (host)
	{
		for (int i = 0; i < member.items.size(); i++)
		{
			LayoutItem& item = member.items[i];
			item.attach(this->host());
		}
	}
}
void BVBoxLayout::clear()
{
	while (member.items.size())
	{
		LayoutItem& item = member.items.first();
		if (item.widget)
		{
			remove(item.widget);
			continue;
		}
		if (item.layout)
		{
			remove(item.layout);
			continue;
		}
		if (item.spacer)
		{
			remove(item.spacer);
			continue;
		}
		if (item.spring)
		{
			remove(item.spring);
			continue;
		}
		break;
	}
	this->dirty();
}

BSizeStretch BVBoxLayout::sizeStretch() const
{
	BSizeStretch stretch = BLayout::sizeStretch();
	if (stretch == BSizeStretch() && member.items.size())
	{
		for (int i = 0; i < member.items.size(); i++)
		{
			LayoutItem& item = member.items[i];
			BSizeStretch sizeStretch = item.sizeStretch();
			stretch.heightStretch() += sizeStretch.heightStretch();
			if (item.spring)
				continue;
			stretch.widthStretch() = bMax(stretch.widthStretch(), sizeStretch.widthStretch());
		}
	}
	return stretch;
}
BSizePolicy BVBoxLayout::sizePolicy() const
{
	BSizePolicy policy = layout_member(this)->sizePolicy;
	if (policy == BSizePolicy())
	{
		for (int i = 0; i < member.items.size(); i++)
		{
			LayoutItem& item = member.items[i];
			BSizePolicy sizePolicy = item.sizePolicy();
			policy.widthPolicy() |= sizePolicy.widthPolicy();
			policy.heightPolicy() |= sizePolicy.heightPolicy();
		}
	}
	return policy;
}
BSize BVBoxLayout::fixedSize() const
{
	BSize fixedSize = layout_member(this)->fixedSize;
	if (fixedSize.width() == 0 || fixedSize.height() == 0)
	{
		int border = this->border();
		int spacing = this->spacing();
		int fixedWidth = fixedSize.width();
		int fixedHeight = fixedSize.height();
		for (int i = 0; i < member.items.size(); i++)
		{
			LayoutItem& item = member.items[i];
			if (item.ignored())
				continue;
			BSize fixSize = item.fixedSize();
			fixedHeight += fixSize.height() + spacing;
			fixedWidth = bMax(fixedWidth, border + border + fixSize.width());
		}
		if (fixedSize.width() == 0)
			fixedSize.width() = fixedWidth;
		if (fixedSize.height() == 0)
			fixedSize.height() = fixedHeight;
	}
	return fixedSize;
}

BSize BVBoxLayout::sizeHint() const
{
	int border = this->border();
	int spacing = this->spacing();
	int maxWidth = border + border;
	int totalHeight = border + border;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		if (item.ignored())
			continue;

		BSize fixSize = item.fixedSize();
		BSize sizeHint = item.sizeHint();
		BSizePolicy policy = item.sizePolicy();

		if (policy.heightPolicy() == Policy_Fixed)
			totalHeight += fixSize.height() + spacing;
		else
			totalHeight += sizeHint.height() + spacing;

		if (policy.widthPolicy() == Policy_Fixed)
			maxWidth = bMax(maxWidth, border + border + fixSize.width());
		else
			maxWidth = bMax(maxWidth, border + border + sizeHint.width());
	}
	BSize hsize(maxWidth, totalHeight - spacing);
	if (hsize.width() < 0)
		hsize.width() = 0;
	if (hsize.height() < 0)
		hsize.height() = 0;
	return hsize;
}
BSize BVBoxLayout::minSize() const
{
	BSize minSize = layout_member(this)->minSize;
	if (member.items.empty())
		return minSize;
	int border = this->border();
	int spacing = this->spacing();
	int minWidth = border + border;
	int minHeight = border + border;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		if (item.ignored())
			continue;

		BSize fixedSize = item.fixedSize();
		BSize itemMinSize = item.minSize();
		BSizePolicy policy = item.sizePolicy();

		if (policy.heightPolicy() == Policy_Fixed)
			minHeight += fixedSize.height() + spacing;
		else
			minHeight += itemMinSize.height() + spacing;

		if (policy.widthPolicy() == Policy_Fixed)
			minWidth = bMax(minWidth, border + border + fixedSize.width());
		else
			minWidth = bMax(minWidth, border + border + itemMinSize.width());
	}
	minSize += BSize(minWidth, minHeight - spacing);
	return minSize;
}
BSize BVBoxLayout::size() const
{
	BSize size;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		BSize itemSize = item.size();
		if (item.spring)
			continue;
		size.height() += itemSize.height();
		size.width() = bMax(size.width(), itemSize.width());
	}
	return size;
}
bool BVBoxLayout::adjust() const
{
	if (!this->isdirty())
		return false;

	if (this->name() == "debug")
		int a = 0;

	if (this->rect().invalid() || member.items.empty())
	{
		for (int i = 0; i < member.items.size(); i++)
		{
			member.items[i].adjust(0, 0, 0, 0);
		}
		this->clean();
		return true;
	}

	const BRect& rect = this->rect();
	int x = rect.x();
	int y = rect.y();
	int w = rect.width();
	int h = rect.height();

	BSize hintSize = this->sizeHint();
	int remain = h - hintSize.height();
	remain = member.distributeHeight(remain);

	int spacing = this->spacing();
	int border = this->border();
	int posX = x + border;
	int posY = y + border;
	int width = rect.width() - border - border;
	for (int i = 0; i < member.items.size(); i++)
	{
		LayoutItem& item = member.items[i];
		int height = item.realSize.height();
		item.adjust(posX, posY, width, height);
		posY += spacing + height;
	}
	this->clean();
	return true;
}

